#include <iostream>
#include <thread>
#include <vector>
#include <rwlock.h>
#include "sqlite3.h"

class Log {
 public:
  static void log(const std::string& msg) {
    std::lock_guard<std::mutex> _(lock_);
    std::cout << "[";
    std::cout << std::this_thread::get_id();
    std::cout << "] ";
    std::cout << msg;
    std::cout << std::endl;
  }
 protected:
  static std::mutex lock_;
};
std::mutex Log::lock_;

class SqliteError : public std::exception {
public:
	explicit SqliteError(int error_code) : m_errno(error_code) {
		m_errmsg=sqlite3_errstr(error_code);
	}
	explicit SqliteError(sqlite3* db) {
		m_errno=sqlite3_errcode(db);
		m_errmsg=sqlite3_errmsg(db);
	}
	SqliteError(const SqliteError& src) = default;
	virtual ~SqliteError() throw() { }
	virtual const char* what() const throw() override { return m_errmsg; }
	int code() const throw() { return m_errno; }
private:
	int m_errno;
	const char* m_errmsg;
};

class Sqlite {
 public:
  Sqlite() { test_open("test.db"); }
  ~Sqlite() { test_close(); }
 public:
  void open(const char *file) {
    int result=sqlite3_open_v2(file, &m_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
		if(result!=SQLITE_OK)
			throw SqliteError(result);
  }
  void close() {
    if(m_db) {
			sqlite3_close_v2(m_db);
			m_db=NULL;
		}
  }
  void simple_execute(const char* lpszSql) {
		int result=sqlite3_exec(m_db, lpszSql, NULL, NULL, NULL);
		if(result!=SQLITE_OK)
			throw SqliteError(result);
	}
  void begin_transaction() {
		simple_execute("BEGIN TRANSACTION");
	}
	void commit() {
		simple_execute("COMMIT TRANSACTION");
	}
	void rollback() {
		simple_execute("ROLLBACK TRANSACTION");
	}
  bool table_exist(const std::string& table) {
    std::string sql = "SELECT * FROM sqlite_master WHERE type ='table' and name ='";
    sql += table;
    sql += "'";

    bool exist = false;
    sqlite3_stmt* stmt = NULL;
    if (sqlite3_prepare_v2(m_db, sql.c_str(), sql.length(), &stmt, NULL) == SQLITE_OK) {
      int r = sqlite3_step(stmt);
      exist = r == SQLITE_ROW;
    }
    sqlite3_finalize(stmt);

    return exist;
  }
 public:
  void test_open(const char *file) {
    open(file);
    Log::log("db opened");
  }
  void test_close() {
    close();
    Log::log("db closed");
  }
  void test_read(int idx) {
    try {
      simple_execute("SELECT * FROM user;");
      std::string msg = std::to_string(idx);
      msg += " db read OK.";
      Log::log(msg);
    } catch (SqliteError &e) {
      std::string msg = std::to_string(idx);
      msg += " db read error: ";
      msg += e.what();
      Log::log(msg);
    }
  }
  void test_write(int idx) {
    try {
      simple_execute("UPDATE user SET age=18,sex=1 WHERE user='dzlua';");
      std::string msg = std::to_string(idx);
      msg += " db write OK.";
      Log::log(msg);
    } catch (SqliteError &e) {
      std::string msg = std::to_string(idx);
      msg += " db write error: ";
      msg += e.what();
      Log::log(msg);
    }
  }
 protected:
  sqlite3* m_db;
};

// Read Write Lock
rwlock::Lock lock_;
unsigned int rw_count_ = 100;
bool use_lock_ = false;
unsigned int read_thread_count_ = 1;
unsigned int write_thread_count_ = 1;

// read thread
void read_thread() {
  Sqlite db;
  for (int i = 0; i < rw_count_; ++i) {
    if (use_lock_) {
      rwlock::LockRead _(lock_);
      db.test_read(i);
    } else {
      db.test_read(i);
    }
  }
}

// write thread
void write_thread() {
  Sqlite db;
  for (int i = 0; i < rw_count_; ++i) {
    if (use_lock_) {
      rwlock::LockWrite _(lock_);
      db.test_write(i);
    } else {
      db.test_write(i);
    }
  }
}

bool init_sqlite() {
  Sqlite db;
  if (db.table_exist("user")) {
    Log::log("drop table user.");
    db.simple_execute("DROP TABLE user;");
  }

  Log::log("create table user.");
  db.simple_execute("CREATE TABLE user("
    "id   INTEGER  NOT NULL PRIMARY KEY AUTOINCREMENT,"
    "user TEXT NOT NULL,"
    "age  INTEGER  NOT NULL,"
    "sex  INTEGER  NOT NULL);"
  );
  
  Log::log("insert data to user.");
  db.simple_execute("INSERT INTO user(user,age,sex) VALUES('dzlua',18,1);");
  db.simple_execute("INSERT INTO user(user,age,sex) VALUES('carry',19,0);");
  db.simple_execute("INSERT INTO user(user,age,sex) VALUES('simtao',20,1);");
  db.simple_execute("INSERT INTO user(user,age,sex) VALUES('three',21,1);");
  return true;
}
int main(int argc, char **argv) {
  if (argc != 5) {
    std::cout << "[Usage] <rw times> <read thread count> <write thread count> <use lock>" << std::endl;
    return 0;
  }

  rw_count_ = atoi(argv[1]);
  read_thread_count_ = atoi(argv[2]);
  write_thread_count_ = atoi(argv[3]);
  use_lock_ = atoi(argv[4]) == 1 ? true : false;

  std::cout << "Read  Write   Times: " << rw_count_ << std::endl;
  std::cout << "Read  Thread  Count: " << read_thread_count_ << std::endl;
  std::cout << "Write Thread  Count: " << write_thread_count_ << std::endl;
  std::cout << "Use Read Write Lock: " << use_lock_ << std::endl;

  if (!init_sqlite())
    return 0;

  std::vector<std::thread> read_threads_;
  std::vector<std::thread> write_threads_;

  for (uint32_t i = 0; i < read_thread_count_; ++i)
    read_threads_.push_back(std::thread(read_thread));
  for (uint32_t i = 0; i < write_thread_count_; ++i)
    write_threads_.push_back(std::thread(write_thread));
  
  for (auto &td : read_threads_)
    td.join();
  for (auto &td : write_threads_)
    td.join();

  return 0;
}